home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 June: Reference Library / Dev.CD Jun 94.toast / Periodicals / develop / develop Issue 17 / develop 17 code / TapBoard / TapBoard.π.text < prev    next >
Encoding:
Text File  |  1994-01-17  |  41.1 KB  |  1,302 lines  |  [TEXT/MPS ]

  1. // Who we are:
  2. constant kAppSymbol := '|TapBoard:Chesley|;
  3. constant kPackageName := "TapBoard:Chesley";
  4.  
  5. // Each square on the board can be empty, contain a newton piece, or contain
  6. // a user piece; for the convenience of the computer move algorithms, the
  7. // board array is made one square larger in each directions and the edges are
  8. // filled in with a special value (0). Also note that if p is one player, -p is
  9. // the other player. These constants are also used to indicate whose turn it is.
  10. constant kEmptySquare := nil;
  11. constant kNewtonPiece := -1;
  12. constant kUserPiece := 1;
  13. constant kBoardEdge := 0;
  14. constant kTieWinner := 0;
  15.  
  16. RemoveScript := func(packageFrame)
  17. begin
  18.    local cursor := Query(GetStores()[0]:GetSoup(ROM_SystemSoupName),
  19.                            {type: 'index, indexPath: 'tag, startKey: kPackageName,
  20.                             validTest: func(item) StrEqual(item.tag, kPackageName)});
  21.    if cursor:Entry() <> nil then
  22.       EntryRemoveFromSoup(cursor:Entry());
  23. end;
  24.  
  25. // ---- End Project Data ----
  26. BoardGames :=
  27.    {
  28.     viewSetupDoneScript:
  29.       func()
  30.       begin
  31.          // Find the saved state
  32.          local stateEntry := :getStateEntry();
  33.          // Is there one?
  34.          if stateEntry = nil then
  35.             // If not, default to the first radio button
  36.             gamePicker:setClusterValue(1)
  37.          else
  38.             begin
  39.                // If there is, restore the state from the entry
  40.                gamePicker:setByName(stateEntry.name);
  41.                currentBoard:restoreState(stateEntry);
  42.             end;
  43.       end,
  44.     viewQuitScript:
  45.       func()
  46.       begin
  47.          // If any board is displayed (which it always will be--
  48.          // we're just being paranoid), save the current state
  49.          if currentBoard <> nil then currentBoard:saveState();
  50.       end,
  51.     announceCredits:
  52.       func()
  53.       begin
  54.          credits:Open();
  55.       end,
  56.     announceWin:
  57.       func(p)
  58.       begin
  59.          // First, make sure the current game display is up to date
  60.          RefreshViews();
  61.       
  62.          // Then bring up the right glance view
  63.          if p = kUserPiece then youWin:Open()
  64.          else if p = kNewtonPiece then iWin:Open()
  65.          else tie:Open();
  66.       end,
  67.     stopWorking:
  68.       func()
  69.       begin
  70.          working:Close();
  71.       end,
  72.     turn:
  73.       func(p)
  74.       begin
  75.          userOrComputer:SetClusterValue(p);
  76.       end,
  77.     viewBounds: {top: 0, left: 0, right: 240, bottom: 336},
  78.     whoseTurn:
  79.       func()
  80.       begin
  81.          return userOrComputer.clusterValue;
  82.       end,
  83.     _proto: protoapp,
  84.     viewJustify: 16,
  85.     currentBoard: nil,
  86.     title: "TapBoard",
  87.     viewSetupFormScript:
  88.       func()
  89.       begin
  90.          boardList := [];
  91.       
  92.          // Remember the original dimensions in case we need them
  93.          local originalWidth := viewBounds.right - viewBounds.left;
  94.          local originalHeight := viewBounds.bottom - viewBounds.top;
  95.       
  96.          // Default the app bounds to the screen bounds (nice on small screens)
  97.          local ap := GetAppParams();
  98.          self.viewBounds := RelBounds(0, ap.appAreaTop, ap.appAreaWidth, ap.appAreaHeight);
  99.       
  100.          // But if the screen's too large for that to look good, center it
  101.          // (We allow for a range of screen sizes to handle future screens)
  102.          if ap.appAreaWidth > (originalWidth+20) then
  103.             self.viewBounds.right := originalWidth;
  104.          if ap.appAreaHeight > (originalHeight+20) then
  105.             begin
  106.                self.viewBounds.top := ap.appAreaTop + (ap.appAreaHeight - originalHeight) div 2;
  107.                self.viewBounds.bottom := self.viewBounds.top + originalHeight;
  108.             end;
  109.       end,
  110.     isTurn:
  111.       func(p)
  112.       begin
  113.          return p = userOrComputer.clusterValue;
  114.       end,
  115.     announceHelp:
  116.       func()
  117.       begin
  118.          help:Open();
  119.       end,
  120.     boardList: nil,
  121.     startWorking:
  122.       func()
  123.       begin
  124.          // Open the view
  125.          working:Open();
  126.          // Then force a refresh of anything that might need it; we're
  127.          // about to do lots of time-consuming work, so the system won't
  128.          // get a chance to do this otherwise
  129.          RefreshViews();
  130.       end,
  131.     viewEffect: 133120,
  132.     getStateEntry:
  133.       func()
  134.       begin
  135.          // Find our one-and-only entry in the System soup, if there is one
  136.          return Query(GetStores()[0]:GetSoup(ROM_SystemSoupName),
  137.                       {type: 'index, indexPath: 'tag, startKey: kPackageName,
  138.                        validTest: func(item) StrEqual(item.tag, kPackageName)}):Entry();
  139.       end,
  140.     newGame:
  141.       func(nm)
  142.       begin
  143.          // Look through all the boards
  144.          local b;
  145.          foreach b in boardList do
  146.             // Are we looking for the one that's currently displayed?
  147.             if nm = nil then
  148.                begin
  149.                   // If so, and if this is it, then clear it
  150.                   if Visible(b) then b:clearBoard();
  151.                end
  152.             // If not, check if this is the one that's been specified
  153.             else if StrEqual(b.name, nm) then
  154.                begin
  155.                   // Set the current board, clear it, show, and set the piece icons
  156.                   currentBoard := b;
  157.                   b:clearBoard();
  158.                   b:Show();
  159.                   player1Sample.icon := b.player1Piece;
  160.                   player1Sample:Dirty();
  161.                   player2Sample.icon := b.player2Piece;
  162.                   player2Sample:Dirty();
  163.                end
  164.             // If this isn't it, then hide it (harmless if already hidden)
  165.             else b:Hide();
  166.       
  167.          // Always start with the user's turn
  168.          :turn(kUserPiece);
  169.       end
  170.    };
  171.  
  172. userOrComputer := /* child of BoardGames */
  173.    {viewBounds: {top: -119, left: 0, right: 175, bottom: -102},
  174.     clusterValue: 1,
  175.     viewJustify: 144,
  176.     _proto: protoradiocluster
  177.    };
  178.  
  179. _view000 := /* child of userOrComputer */
  180.    {buttonValue: 1,
  181.     viewBounds: {left: 9, top: 0, right: 73, bottom: 16},
  182.     text: "Your Move",
  183.     _proto: protoradiobutton
  184.    };
  185.  
  186.  
  187.  
  188. _view001 := /* child of userOrComputer */
  189.    {buttonValue: -1,
  190.     viewBounds: {left: 81, top: 0, right: 185, bottom: 16},
  191.     text: "Computer's Move",
  192.     _proto: protoradiobutton
  193.    };
  194.  
  195.  
  196. // View userOrComputer is accesible from BoardGames
  197.  
  198.  
  199.  
  200. player2Sample := /* child of BoardGames */
  201.    {viewflags: 529,
  202.     icon: nil,
  203.     viewFormat: 256,
  204.     viewBounds: {top: -100, left: 42, right: 74, bottom: -68},
  205.     thePicture: GetPictAsBits("BPPicture", nil),
  206.     viewClickScript:
  207.       func(unit)
  208.       begin
  209.          // No ink (we're tapping, not drawing)
  210.          InkOff(unit);
  211.       
  212.          // Make a nice little click to give the user warm fuzzies
  213.          PlaySound(ROM_click);
  214.       
  215.          // Make it the Newton's turn
  216.          :turn(kNewtonPiece);
  217.       
  218.          return true;
  219.       end,
  220.     viewJustify: 144,
  221.     viewclass: 76
  222.    };
  223. // View player2Sample is accesible from BoardGames
  224.  
  225.  
  226.  
  227. player1Sample := /* child of BoardGames */
  228.    {viewflags: 529,
  229.     icon: nil,
  230.     viewFormat: 256,
  231.     viewBounds: {top: -100, left: -42, right: -10, bottom: -68}
  232.     ,
  233.     thePicture: nil,
  234.     viewClickScript:
  235.       func(unit)
  236.       begin
  237.          // No ink (we're tapping, not drawing)
  238.          InkOff(unit);
  239.       
  240.          // Make a nice little click to give the user warm fuzzies
  241.          PlaySound(ROM_click);
  242.       
  243.          // Make it the user's turn
  244.          :turn(kUserPiece);
  245.       
  246.          return true;
  247.       end,
  248.     viewJustify: 144,
  249.     viewclass: 76
  250.    };
  251. // View player1Sample is accesible from BoardGames
  252.  
  253.  
  254.  
  255. iWin := /* child of BoardGames */
  256.    {viewBounds: {top: -121, left: 0, right: 218, bottom: -63},
  257.     viewEffect: 54659072,
  258.     text: "I win!\nThat was easy!",
  259.     viewfont: fancyFont12+tsBold,
  260.     viewJustify: 150,
  261.     viewIdleFrequency: 10000,
  262.     viewflags: 512,
  263.     _proto: protoglance
  264.    };
  265. // View iWin is accesible from BoardGames
  266.  
  267.  
  268.  
  269. youWin := /* child of BoardGames */
  270.    {viewBounds: {top: -121, left: 0, right: 218, bottom: -63},
  271.     viewEffect: 54659072,
  272.     text: "You win!\nProbably just luck...",
  273.     viewfont: fancyFont12+tsBold,
  274.     viewJustify: 150,
  275.     viewIdleFrequency: 10000,
  276.     viewflags: 512,
  277.     _proto: protoglance
  278.    };
  279. // View youWin is accesible from BoardGames
  280.  
  281.  
  282.  
  283. tie := /* child of BoardGames */
  284.    {viewBounds: {top: -121, left: 0, right: 218, bottom: -63},
  285.     viewEffect: 54659072,
  286.     text: "It's a tie!\nLet's play again.",
  287.     viewfont: fancyFont12+tsBold,
  288.     viewJustify: 150,
  289.     viewIdleFrequency: 10000,
  290.     viewflags: 512,
  291.     _proto: protoglance
  292.    };
  293. // View tie is accesible from BoardGames
  294.  
  295.  
  296.  
  297. credits := /* child of BoardGames */
  298.    {viewBounds: {top: 0, left: 0, right: 200, bottom: 280},
  299.     viewflags: 528,
  300.     viewFormat: 67175153,
  301.     viewJustify: 80,
  302.     _proto: protofloatngo
  303.    };
  304.  
  305. _view002 := /* child of credits */
  306.    {text: "TapBoard 1.1.1\nby\nHarry R. Chesley",
  307.     viewBounds: {left: 0, top: 25, right: 180, bottom: 81},
  308.     viewJustify: 18,
  309.     viewfont: fancyFont12+tsBold,
  310.     _proto: protostatictext
  311.    };
  312.  
  313.  
  314.  
  315. _view003 := /* child of credits */
  316.    {viewflags: 1,
  317.     viewFormat: 256,
  318.     viewLineSpacing: 12,
  319.     viewfont: fancyFont10,
  320.     viewBounds: {left: 0, top: 97, right: 181, bottom: 273},
  321.     text:
  322.       "TapBoard is a collection of\ntap-to-move board games for\nthe Newton\u2122\u. If you like it,\nsend me Newton-mail and let\nme know.\n\nTapBoard is public domain.\nYou may freely distribute\nit, but must include this\nnotice.\n\nEnjoy."
  323.     ,
  324.     viewJustify: 18,
  325.     viewclass: 81
  326.    };
  327.  
  328.  
  329. // View credits is accesible from BoardGames
  330.  
  331.  
  332.  
  333. _view004 := /* child of BoardGames */
  334.    {viewBounds: {top: -16, left: 101, right: 131, bottom: -3},
  335.     text: "Help",
  336.     buttonClickScript:
  337.       func()
  338.       begin
  339.          :announceHelp();
  340.       end,
  341.     viewJustify: 8388742,
  342.     _proto: prototextbutton
  343.    };
  344.  
  345.  
  346.  
  347. _view005 := /* child of BoardGames */
  348.    {text: "New Game",
  349.     buttonClickScript:
  350.       func()
  351.       begin
  352.          :newGame(nil);
  353.       end,
  354.     viewBounds: {top: -16, left: 29, right: 91, bottom: -3},
  355.     viewJustify: 8388742,
  356.     _proto: prototextbutton
  357.    };
  358.  
  359.  
  360.  
  361. _view006 := /* child of BoardGames */
  362.    {viewBounds: {top: -16, left: 141, right: 187, bottom: -3},
  363.     text: "Credits",
  364.     buttonClickScript:
  365.       func()
  366.       begin
  367.          :announceCredits();
  368.       end,
  369.     viewJustify: 8388742,
  370.     _proto: prototextbutton
  371.    };
  372.  
  373.  
  374.  
  375. working := /* child of BoardGames */
  376.    {viewBounds: {top: -57, left: 0, right: 218, bottom: -31},
  377.     text: "Working...",
  378.     viewfont: fancyFont18+tsBold,
  379.     viewJustify: 8388758,
  380.     viewflags: 16,
  381.     viewFormat: 66129,
  382.     viewEffect: 0,
  383.     viewLineSpacing: 20,
  384.     viewclass: 81
  385.    };
  386. // View working is accesible from BoardGames
  387.  
  388.  
  389.  
  390. help := /* child of BoardGames */
  391.    {viewBounds: {top: 0, left: 0, right: 200, bottom: 290},
  392.     viewflags: 528,
  393.     viewFormat: 67175153,
  394.     viewJustify: 90,
  395.     currentHelp: 0,
  396.     selectHelp:
  397.       func(x)
  398.       begin
  399.          SetValue(mainText, 'text, GetVar(helpSyms[x]));
  400.       end,
  401.     viewSetupDoneScript:
  402.       func()
  403.       begin
  404.          :selectHelp(0);
  405.       end,
  406.     helpSyms: ['generalHelp, 'tictactoeHelp, 'gomokuHelp, 'reversiHelp],
  407.     generalHelp:
  408.       "Just tap in a square to make\na move there.\n\nIf you want the computer to\nmove first, click on the\n\"Computer's Move\" radio\nbutton. If you want to \"pass\"\n(which is illegal in Tic-tac-\ntoe and Gomoku, but the\nprogram still lets you do it),\nclick on \"Computer's Move\".\n\nWhile the computer is\nworking on its move, the\n\"Computer's Move\" radio\nbutton is set, and a big\n\"Working\" sign comes up.\nWhen the computer is done,\nor it decides to \"pass,\"\nthe \"Your Move\" radio\nbutton will be set."
  409.     ,
  410.     tictactoeHelp:
  411.       "Tic-tac-toe is the classic\ngame, played on a 3-by-3\nboard. You try to get three\nin a row, vertical, horizontal,\nor diagonal, before the\ncomputer does the same.\n\nIt's not too hard to beat\nthe computer. But if you want\nmore of a challenge, let the\ncomputer move first by\nclicking on the \"Computer's\nMove\" radio button at the\nstart of the game."
  412.     ,
  413.     gomokuHelp:
  414.       "Try for five in a row before\nthe computer does. The row\ncan be vertical, horizontal,\nor diagonal.\n\nIt's possible to beat the\ncomputer, but it isn't easy."
  415.     ,
  416.     reversiHelp:
  417.       "Try to end up with more\npieces than the computer\ndoes when there are no\nmore legal moves.\n\nEach move must \"trap\" some\nof your opponent's pieces\nbetween two of your pieces.\nAll the trapped pieces\nflip over to become your\npieces (hence the name).\n\nIf you can't move, you\ncan \"pass\" by tapping on the\n\"Computer's Move\" radio\nbutton. The computer may do\nthe same by setting the \"Your\nMove\" radio button."
  418.     ,
  419.     _proto: protofloatngo
  420.    };
  421.  
  422. _view007 := /* child of help */
  423.    {viewBounds: {left: 18, top: 10, right: 175, bottom: 24},
  424.     labelCommands: ["General", "Tic-tac-toe", "Gomoku", "Reversi"],
  425.     text: "Help Topic:",
  426.     viewfont: ROM_fontSystem9Bold,
  427.     labelActionScript:
  428.       func(cmd)
  429.       begin
  430.          :selectHelp(cmd);
  431.       end,
  432.     viewJustify: 8388624,
  433.     _proto: protolabelpicker
  434.    };
  435.  
  436.  
  437.  
  438. mainText := /* child of help */
  439.    {viewflags: 17,
  440.     viewFormat: 256,
  441.     viewLineSpacing: 12,
  442.     viewfont: fancyFont10,
  443.     viewBounds: {top: 27, left: 0, right: 180, bottom: 299},
  444.     text: "foo",
  445.     viewJustify: 18,
  446.     viewclass: 81
  447.    };
  448. // View mainText is accesible from help
  449.  
  450.  
  451. // View help is accesible from BoardGames
  452.  
  453.  
  454.  
  455. _view008 := /* child of BoardGames */
  456.    {viewflags: 529,
  457.     viewFormat: nil,
  458.     viewBounds: {left: 0, top: 0, right: 240, bottom: 16},
  459.     viewClickScript:
  460.       func(unit)
  461.       begin
  462.          PlaySound(ROM_click);
  463.          :announceCredits();
  464.       end,
  465.     viewclass: 74
  466.    };
  467.  
  468.  
  469.  
  470. gamePicker := /* child of BoardGames */
  471.    {viewBounds: {top: -51, left: 0, right: 215, bottom: -36},
  472.     ClusterChanged:
  473.       func()
  474.       begin
  475.          // Find the name of the new button
  476.          foreach t in stepChildren do
  477.             if t.buttonValue = clusterValue then
  478.                begin
  479.                   // Found it, start a new game of that name
  480.                   :newGame(t.text);
  481.                   return;
  482.                end
  483.       end,
  484.     setByName:
  485.       func(nm)
  486.       begin
  487.          // Find the radio button with this name, and set it
  488.          local t;
  489.          foreach t in :ChildViewFrames() do
  490.             if StrEqual(t.text,nm) then
  491.                begin
  492.                   if clusterValue <> t.buttonValue then :setClusterValue(t.buttonValue);
  493.                   return;
  494.                end
  495.       end,
  496.     viewJustify: 144,
  497.     _proto: protoradiocluster
  498.    };
  499.  
  500. _view009 := /* child of gamePicker */
  501.    {viewBounds: {left: 7, top: -1, right: 86, bottom: 15},
  502.     text: "Tic-tac-toe",
  503.     buttonValue: 1,
  504.     _proto: protoradiobutton
  505.    };
  506.  
  507.  
  508.  
  509. _view010 := /* child of gamePicker */
  510.    {buttonValue: 2,
  511.     viewBounds: {left: 87, top: -1, right: 151, bottom: 15},
  512.     text: "Gomoku",
  513.     _proto: protoradiobutton
  514.    };
  515.  
  516.  
  517.  
  518. _view011 := /* child of gamePicker */
  519.    {buttonValue: 3,
  520.     viewBounds: {left: 151, top: -1, right: 207, bottom: 15},
  521.     text: "Reversi",
  522.     _proto: protoradiobutton
  523.    };
  524.  
  525.  
  526. // View gamePicker is accesible from BoardGames
  527.  
  528.  
  529.  
  530.  
  531. Board :=
  532.    {
  533.     makeComputerMove:
  534.       func()
  535.       begin
  536.          // By default, we just do something random
  537.          // This is always overridden
  538.          :makeRandomMove(kNewtonPiece);
  539.       end,
  540.     squareBounds:
  541.       func(x, y)
  542.       begin
  543.          local width := :squareWidth();
  544.          local height := :squareHeight();
  545.          return RelBounds((x-1)*width+1, (y-1)*height+1, width-1, height-1);
  546.       end,
  547.     player2Piece: GetPictAsBits("BPPicture", nil),
  548.     viewSetupDoneScript:
  549.       func()
  550.       begin
  551.          // Build the board display
  552.          // This builds a closed set of squares, but can be overridden
  553.       
  554.          local height := :LocalBox().bottom-1;
  555.          local width := :LocalBox().right-1;
  556.       
  557.          backgroundDrawing := [];
  558.       
  559.          local x;
  560.          for x := 0 to width by :squareWidth() do
  561.             AddArraySlot(backgroundDrawing, MakeLine(x,0,x,height));
  562.       
  563.          local y;
  564.          for y := 0 to height by :squareHeight() do
  565.             AddArraySlot(backgroundDrawing, MakeLine(0,y,width,y));
  566.       end,
  567.     viewFormat: 256,
  568.     viewDrawScript:
  569.       func()
  570.       begin
  571.          :DrawShape(backgroundDrawing, nil);
  572.       end,
  573.     boardArray: nil,
  574.     Move:
  575.       func(p, x, y)
  576.       begin
  577.          // Check if this is a reasonable thing to do
  578.          if :isTurn(p) and :validMove(p, x, y) then
  579.             begin
  580.                // Add the piece to the board
  581.                :addPiece(p, x, y);
  582.                // If this was a winner, let the user know
  583.                if :winningMove(p, x, y) then
  584.                   begin
  585.                      winner := p;
  586.                      :announceWin(p);
  587.                   end
  588.                // If this was a tie-maker, let the user know
  589.                else if :tieGame() then
  590.                   begin
  591.                      winner := kTieWinner;
  592.                      :turn(kTieWinner);
  593.                      :announceWin(kTieWinner);
  594.                   end
  595.                // Switch whose turn it is
  596.                else :turn(-p);
  597.             end;
  598.       end,
  599.     viewflags: 1553,
  600.     viewIdleScript:
  601.       func()
  602.       begin
  603.          // If we're real, it's the computer's turn, and there's no winner...
  604.          if Visible(self) and :isTurn(kNewtonPiece) and (winner = nil) then
  605.             begin
  606.                // Put up the "Working..." sign, and figure the computer move
  607.                :startWorking();
  608.                :makeComputerMove();
  609.                :stopWorking();
  610.             end;
  611.       
  612.          // Try again in a quarter of a second
  613.          return 250;
  614.       end,
  615.     setupBoard:
  616.       func()
  617.       begin
  618.          // Normally, there is no board set-up needed
  619.          // This is overridden to add initial pieces to the board
  620.       end,
  621.     viewBounds: {left: 0, top: 32, right: 161, bottom: 193},
  622.     clearBoard:
  623.       func()
  624.       begin
  625.          // Remove all the piece views, and redisplay
  626.          local v;
  627.          foreach v in :ChildViewFrames() do RemoveStepView(self, v);
  628.          :Dirty();
  629.       
  630.          // Clear out the boardArray
  631.          local x, y;
  632.          for x := 1 to squaresWide do
  633.             for y := 1 to squaresHigh do
  634.                boardArray[x][y] := kEmptySquare;
  635.       
  636.          // Reset the squares left
  637.          squaresLeft := squaresWide * squaresHigh;
  638.       
  639.          // No winner yet
  640.          winner := nil;
  641.       
  642.          // Do any game-specific board set-up
  643.          :setupBoard();
  644.       end,
  645.     player1Piece: GetPictAsBits("WPPicture", nil),
  646.     makeRandomMove:
  647.       func(p)
  648.       begin
  649.          // Try ten times to find a reasonable random move
  650.          local i, x, y;
  651.          for i := 1 to 10 do
  652.             begin
  653.                x := Random(1,squaresWide);
  654.                y := Random(1,squaresHigh);
  655.                if :validMove(p, x, y) then
  656.                   begin
  657.                      :move(p, x, y);
  658.                      return;
  659.                   end;
  660.             end;
  661.       
  662.          // If that doesn't work, then just pick the first linear move
  663.          for x := 1 to squaresWide do
  664.             for y := 1 to squaresHigh do
  665.                if :validMove(p, x, y) then
  666.                   begin
  667.                      :move(p, x, y);
  668.                      return;
  669.                   end;
  670.       end,
  671.     squaresLeft: nil,
  672.     squareOfY:
  673.       func(y)
  674.       begin
  675.          local gb := :GlobalBox();
  676.          if (y < gb.top) or (y > gb.bottom) then return 0;
  677.          else return ((y - gb.top) div :squareHeight()) + 1;
  678.       end,
  679.     addPiece:
  680.       func(p, x, y)
  681.       begin
  682.          // Mark the new piece in the boardArray
  683.          boardArray[x][y] := p;
  684.       
  685.          // Check if there's already a view there in the view list
  686.          local bounds := :squareBounds(x,y);
  687.          local i := if p = kUserPiece then player1Piece else player2Piece;
  688.          local v;
  689.          foreach v in :ChildViewFrames() do
  690.             if (v.viewBounds.top = bounds.top) and (v.viewBounds.left = bounds.left) then
  691.                begin
  692.                   // If there is, replace the icon and redisplay
  693.                   SetValue(v, 'icon, i);
  694.                   return;
  695.                end;
  696.       
  697.          // One less square available
  698.          squaresLeft := squaresLeft - 1;
  699.       
  700.          // Create, add in, and display the new view
  701.          AddStepView(self,
  702.                   {
  703.                   viewClass: clPictureView,
  704.                   viewBounds: :squareBounds(x, y),
  705.                   viewFlags: vVisible,
  706.                   icon: i
  707.                   }):Dirty();
  708.       end,
  709.     winner: nil,
  710.     validMove:
  711.       func(p, x, y)
  712.       begin
  713.          // If it's an empty space, then it's legal to move there
  714.          // This function may be overridden
  715.          return boardArray[x][y] = kEmptySquare;
  716.       end,
  717.     squareWidth:
  718.       func()
  719.       begin
  720.          return :LocalBox().right div squaresWide;
  721.       end,
  722.     winningMove:
  723.       func(p, x, y)
  724.       begin
  725.          // By default, the computer never wins and the user wins when the board is full
  726.          // This is always overridden, but we leave it in because it can be handy during
  727.          // the early stages of developing a new game
  728.          if p = kNewtonPiece then return nil
  729.          else return squaresLeft = 0;
  730.       end,
  731.     backgroundDrawing: nil,
  732.     viewSetupFormScript:
  733.       func()
  734.       begin
  735.          // Register ourselves with the app
  736.          AddArraySlot(boardList, self);
  737.       
  738.          // Make the board array; we make it one entry larger in each direction
  739.          // than the board, which is nice sometimes when figuring out moves
  740.          boardArray := Array(squaresWide+2, kEmptySquare);
  741.          local i;
  742.          for i := 0 to squaresWide+1 do
  743.             begin
  744.                boardArray[i] := Array(squaresHigh+2,
  745.                                        if (i = 0) or (i = squaresWide+1) then
  746.                                           kBoardEdge else kEmptySquare);
  747.                boardArray[i][0] := kBoardEdge;
  748.                boardArray[i][squaresHigh+1] := kBoardEdge;
  749.             end;
  750.       
  751.          // Reset the number of squares left
  752.          squaresLeft := squaresWide * squaresHigh;
  753.       
  754.          // No winner yet
  755.          winner := nil;
  756.       
  757.          // Do any game-specfic set-up
  758.          :setupBoard();
  759.       
  760.          // Have our idle method called
  761.          :SetUpIdle(250);
  762.       end,
  763.     viewClickScript:
  764.       func(unit)
  765.       begin
  766.          // No ink (we're tapping, not drawing)
  767.          InkOff(unit);
  768.       
  769.          // Make a nice little click to give the user warm fuzzies
  770.          PlaySound(ROM_click);
  771.       
  772.          // But let the normal processing handle tracking and such
  773.          return nil;
  774.       end,
  775.     squareOfX:
  776.       func(x)
  777.       begin
  778.          local gb := :GlobalBox();
  779.          if (x < gb.left) or (x > gb.right) then return 0;
  780.          else return ((x - gb.left) div :squareWidth()) + 1;
  781.       end,
  782.     squareHeight:
  783.       func()
  784.       begin
  785.          return :LocalBox().bottom div squaresHigh;
  786.       end,
  787.     tieGame:
  788.       func()
  789.       begin
  790.          // It's a tie if there's nothing left to do
  791.          // This can be overridden
  792.          return squaresLeft = 0;
  793.       end,
  794.     saveState:
  795.       func()
  796.       begin
  797.          // Get the existing state entry, if any
  798.          local stateEntry := :getStateEntry();
  799.          // If there isn't one yet, make one
  800.          if stateEntry = nil then
  801.             stateEntry := GetStores()[0]:GetSoup(ROM_SystemSoupName)
  802.                                           :Add({Tag: kPackageName});
  803.          // If we can't make one, well, uh, let's just forget the whole thing
  804.          if stateEntry = nil then return;
  805.       
  806.          // Build an array of pieces and their positions from the boardArray
  807.          local x, y;
  808.          local pieces := [];
  809.          local ba := boardArray;
  810.          for x := 1 to squaresWide do
  811.             for y := 1 to squaresHigh do
  812.                if ba[x][y] <> kEmptySquare then
  813.                      AddArraySlot(pieces, {player: ba[x][y], x: x, y: y});
  814.       
  815.          // Remember which game this is, the piece positions, whose turn it is, and the winner
  816.          stateEntry.name := name;
  817.          stateEntry.pieces := pieces;
  818.          stateEntry.whichTurn := :whoseTurn();
  819.          stateEntry.winner := winner;
  820.       
  821.          // Tell the soup to save the changed entry
  822.          EntryChange(stateEntry);
  823.       end,
  824.     restoreState:
  825.       func(stateEntry)
  826.       begin
  827.          // For each piece stored in the state entry, add it to the board
  828.          local p;
  829.          foreach p in stateEntry.pieces do
  830.             :addPiece(p.player, p.x, p.y);
  831.       
  832.          // Set whose turn it is
  833.          :turn(stateEntry.whichTurn);
  834.       
  835.          // Set the winner, if there is one
  836.          winner := stateEntry.winner;
  837.       end,
  838.     viewJustify: 16,
  839.     viewStrokeScript:
  840.       func(unit)
  841.       begin
  842.          // Find out where we clicked to start with
  843.          local originalX := :squareOfX(GetPoint(firstX, unit));
  844.          local originalY := :squareOfY(GetPoint(firstY, unit));
  845.       
  846.          // If we ended where we started, then make the move
  847.          if (originalX <> 0) and (originalY <> 0) and
  848.                (originalX = :squareOfX(GetPoint(finalX, unit))) and
  849.                (originalY = :squareOfY(GetPoint(finalY, unit))) then
  850.             :move(kUserPiece, originalX, originalY);
  851.       
  852.          return true;
  853.       end,
  854.     viewclass: 74
  855.    };
  856.  
  857.  
  858. Tictactoe := /* child of BoardGames */
  859.    {
  860.     makeComputerMove:
  861.       func()
  862.       begin
  863.          local moves := [];
  864.          local bestScore := -1000;
  865.          local newScore;
  866.          local x, y;
  867.       
  868.          // Try each board position
  869.          for x := 1 to squaresWide do
  870.             for y := 1 to squaresHigh do
  871.                if boardArray[x][y] = kEmptySquare then
  872.                   begin
  873.                      // Look ahead to score this move
  874.                      newScore := :tryMove(kUserPiece, kNewtonPiece, x, y);
  875.                      // If this is the best one yet, remember only it
  876.                      if newScore > bestScore then
  877.                         begin
  878.                            moves := [];
  879.                            bestScore := newScore;
  880.                         end;
  881.                      // If it's tied for best move, remember it too
  882.                      if newScore = bestScore then AddArraySlot(moves, {mvx: x, mvy: y});
  883.                   end;
  884.       
  885.          // If there are any good moves...
  886.          if Length(moves) > 0 then
  887.             begin
  888.                // Make the move
  889.                local move := moves[Random(0,Length(moves)-1)];
  890.                :move(kNewtonPiece, move.mvx, move.mvy);
  891.             end
  892.          // If there are no good ones, then make a random one and pray
  893.          else :makeRandomMove(kNewtonPiece);
  894.       end,
  895.     name: "Tic-tac-toe",
  896.     player1Piece: GetPictAsBits("XPicture", nil),
  897.     player2Piece: GetPictAsBits("OPicture", nil),
  898.     squaresHigh: 3,
  899.     squaresWide: 3
  900.     ,
  901.     tryMove:
  902.       func(d, p, x, y)
  903.       begin
  904.          // First, guess based on heuristics
  905.          // (Note: We use a quoted array here to save execution time; quoting
  906.          //  it means there will only be one copy -- without the quote a new
  907.          //  one would be constructed each time at run-time; of course, this
  908.          //  also means we can't change the contents, but we don't want to)
  909.          local score := '[5, 0, 5,   5, 10, 5,   5, 0, 5][x+x+x+y-4];
  910.       
  911.          // Make the move internally (we'll retract it later)
  912.          local ba := boardArray;
  913.          ba[x][y] := p;
  914.          squaresLeft := squaresLeft - 1;
  915.       
  916.          // If it's a winner, great, give it a high score
  917.          if :winningMove(p, x, y) then score := 100;
  918.          // If there's anything to look ahead to, do it
  919.          else if (squaresLeft <> 0) and (d > 0) then
  920.             begin
  921.                local worstResponse := 1000;
  922.                local newResponse;
  923.                local x2, y2;
  924.                // Try every board position
  925.                for x2 := 1 to squaresWide do
  926.                   for y2 := 1 to squaresHigh do
  927.                      if ba[x2][y2] = kEmptySquare then
  928.                         begin
  929.                            // How good is this one?
  930.                            newResponse := :tryMove(d-1, -p, x2, y2);
  931.                            // If it's a looser, then give up quick
  932.                            if newResponse >= 100 then
  933.                               begin
  934.                                  ba[x][y] := nil;
  935.                                  squaresLeft := squaresLeft + 1;
  936.                                  return -100;
  937.                               end;
  938.                            // If it's the least bad one so far, remember that
  939.                            if newResponse < worstResponse then worstResponse := newResponse;
  940.                         end;
  941.                score := score - worstResponse;
  942.             end;
  943.       
  944.          // Retract the move
  945.          ba[x][y] := kEmptySquare;
  946.          squaresLeft := squaresLeft + 1;
  947.       
  948.          return score;
  949.       end,
  950.     viewSetupDoneScript:
  951.       func()
  952.       begin
  953.          // Make an open cross-hatch (the default function does a closed board)
  954.          local height := :LocalBox().bottom-1;
  955.          local width := :LocalBox().right-1;
  956.       
  957.          local xIncr := :squareWidth();
  958.          local yIncr := :squareHeight();
  959.       
  960.          backgroundDrawing := [];
  961.       
  962.          local x;
  963.          for x := xIncr to width - xIncr by xIncr do
  964.             AddArraySlot(backgroundDrawing, MakeLine(x,0,x,height));
  965.       
  966.          local y;
  967.          for y := yIncr to height - yIncr by yIncr do
  968.             AddArraySlot(backgroundDrawing, MakeLine(0,y,width,y));
  969.       end,
  970.     winningMove:
  971.       func(p, x, y)
  972.       begin
  973.          local ba := boardArray;
  974.          return
  975.             ((ba[x][1] = p) and (ba[x][2] = p) and (ba[x][3] = p)) or
  976.             ((ba[1][y] = p) and (ba[2][y] = p) and (ba[3][y] = p)) or
  977.             ((ba[1][1] = p) and (ba[2][2] = p) and (ba[3][3] = p)) or
  978.             ((ba[3][1] = p) and (ba[2][2] = p) and (ba[1][3] = p));
  979.       end,
  980.     _proto: Board
  981.    };
  982. // View Tictactoe is accesible from BoardGames
  983.  
  984.  
  985.  
  986. Reversi := /* child of BoardGames */
  987.    {
  988.     Move:
  989.       func(p, x, y)
  990.       begin
  991.          local x2, y2, c, d;
  992.          local ba := boardArray;
  993.       
  994.          // Is it the proper turn and a valid move?
  995.          if :isTurn(p) and :validMove(p, x, y) then
  996.             begin
  997.                // Check each horizontal, vertical, and diagonal direction
  998.                for c := -1 to 1 do
  999.                   for d := -1 to 1 do
  1000.                      begin
  1001.                         // Scan out until we reach the edge or a piece
  1002.                         x2 := x; y2 := y;
  1003.                         while ba[x2+c][y2+d] = -p do
  1004.                            begin
  1005.                               x2 := x2 + c; y2 := y2 + d;
  1006.                            end;
  1007.                         // If we hit an opposing piece, then scan back
  1008.                         // again and flip the pieces to the new color
  1009.                         if ba[x2+c][y2+d] = p then
  1010.                            loop
  1011.                               begin
  1012.                                  :addPiece(p,x2,y2);
  1013.                                  if (x2 = x) and (y2 = y) then break;
  1014.                                  x2 := x2 - c; y2 := y2 - d;
  1015.                               end;
  1016.                      end;
  1017.                // Switch whose turn it is
  1018.                :turn(-p);
  1019.             end;
  1020.       end,
  1021.     makeComputerMove:
  1022.       func()
  1023.       begin
  1024.          local h, q, x, y, x2, y2, c, d, k, xm, ym;
  1025.          local ba := boardArray;
  1026.       
  1027.          // Check through all the possible moves
  1028.          h := 0;
  1029.          for x := 1 to squaresWide do
  1030.             for y := 1 to squaresHigh do
  1031.                if ba[x][y] = kEmptySquare then
  1032.                   begin
  1033.                      // Figure out how many squares would become ours
  1034.                      q := 0;
  1035.                      for c := -1 to 1 do
  1036.                         for d := -1 to 1 do
  1037.                            begin
  1038.                               k := 0;
  1039.                               x2 := x; y2 := y;
  1040.                               while ba[x2+c][y2+d] = kUserPiece do
  1041.                                  begin
  1042.                                     k := k+1;
  1043.                                     x2 := x2 + c; y2 := y2 + d;
  1044.                                  end;
  1045.                               if ba[x2+c][y2+d] = kNewtonPiece then q := q + k;
  1046.                            end;
  1047.                      // If it's an edge move, double its value
  1048.                      if (x = 1) or (x = 8) or (y = 1) or (y = 8) then q := q*2
  1049.                      // If it's a next-to-edge move, half its value
  1050.                      else if (x = 2) or (x = 7) or (y = 2) or (y = 7) then q := q/2;
  1051.                      // If it's an edge next to a corner, half its value
  1052.                      if ( ((x = 1) or (x = 8)) and ((y = 2) or (y = 7)) ) or
  1053.                            ( ((x = 2) or (x = 7)) and ((y = 1) or (y = 8)) ) then q := q/2;
  1054.                      // If it's the highest yet, or the same but chance favors it...
  1055.                      if (q >= h) or ((q = h) and (Random(0,2) <> 0)) then
  1056.                         begin
  1057.                            // Remember this move
  1058.                            h := q;
  1059.                            xm := x; ym := y;
  1060.                         end;
  1061.                   end;
  1062.       
  1063.          // If we found a good move, make it
  1064.          if h <> 0 then
  1065.             begin
  1066.                :move(kNewtonPiece, xm, ym);
  1067.                if squaresLeft > 0 then return;
  1068.             end;
  1069.       
  1070.          // Now check for a winning or tying situation
  1071.          // We couldn't find a move or we wouldn't be here
  1072.          // Check if the user has a move open
  1073.          local pc := 0;
  1074.          local cc := 0;
  1075.          for x := 1 to squaresWide do
  1076.             for y := 1 to squaresHigh do
  1077.                if (ba[x][y] = kEmptySquare) and :validMove(kUserPiece, x, y) then
  1078.                   begin
  1079.                      :turn(kUserPiece);
  1080.                      return;
  1081.                   end
  1082.                else if ba[x][y] = kUserPiece then pc := pc + 1
  1083.                else if ba[x][y] = kNewtonPiece then cc := cc + 1;
  1084.       
  1085.          // If not, then set the winner according to the piece counts
  1086.          if pc > cc then winner := kUserPiece
  1087.          else if cc > pc then winner := kNewtonPiece
  1088.          else winner := kTieWinner;
  1089.          :announceWin(winner);
  1090.          if winner = kTieWinner then :turn(kTieWinner);
  1091.       end,
  1092.     name: "Reversi",
  1093.     setupBoard:
  1094.       func()
  1095.       begin
  1096.          :addPiece(kNewtonPiece, 4, 4);
  1097.          :addPiece(kNewtonPiece, 5, 5);
  1098.          :addPiece(kUserPiece, 4, 5);
  1099.          :addPiece(kUserPiece, 5, 4);
  1100.       end,
  1101.     squaresHigh: 8,
  1102.     squaresWide: 8,
  1103.     tieGame:
  1104.       func()
  1105.       begin
  1106.          // In this game, we compute the winner whent the computer moves
  1107.          return winner = kTieWinner;
  1108.       end,
  1109.     validMove:
  1110.       func(p, x, y)
  1111.       begin
  1112.          // If the space is already taken, it isn't valid
  1113.          if boardArray[x][y] <> kEmptySquare then return nil;
  1114.       
  1115.          local c, d, x2, y2;
  1116.          local ba := boardArray;
  1117.       
  1118.          // Check is all directions
  1119.          for c := -1 to 1 do
  1120.             for d := -1 to 1 do
  1121.                begin
  1122.                   // Look for a piece
  1123.                   x2 := x+c; y2 := y+d;
  1124.                   while ba[x2][y2] = -p do
  1125.                      begin
  1126.                         x2 := x2 + c; y2 := y2 + d;
  1127.                      end;
  1128.                   // If it's one of our pieces,
  1129.                   // and there were some of the other color in between
  1130.                   // then it's valid
  1131.                   if (ba[x2][y2] = p) and (((x2-c) <> x) or ((y2-d) <> y)) then
  1132.                      return true;
  1133.                end;
  1134.       
  1135.          // Otherwise, it isn't valid
  1136.          return nil;
  1137.       end,
  1138.     winningMove:
  1139.       func(p, x, y)
  1140.       begin
  1141.          // In this game, we compute the winner whent the computer moves
  1142.          return winner = p;
  1143.       end,
  1144.     _proto: Board
  1145.    };
  1146. // View Reversi is accesible from BoardGames
  1147.  
  1148.  
  1149.  
  1150. Gomoku := /* child of BoardGames */
  1151.    {
  1152.     makeComputerMove:
  1153.       func()
  1154.       begin
  1155.          local t, gx, gy, h1, l, p, k, m, i;
  1156.          local xInc, yInc, x, y, x2, y2, s;
  1157.          local ba := boardArray;
  1158.       
  1159.          // Make three passes over the board
  1160.          for t := 1 to 3 do
  1161.             begin
  1162.                // First and third checking our moves, second checking his
  1163.                p := if t = 2 then kUserPiece else kNewtonPiece;
  1164.                h1 := 0;
  1165.                l := 0;
  1166.                // Look at each possible move
  1167.                for x := 1 to squaresWide do
  1168.                   for y := 1 to squaresHigh do
  1169.                      if ba[x][y] = kEmptySquare then
  1170.                         begin
  1171.                            // Check the counts in each direction
  1172.                            m := 0;
  1173.                            for i := 0 to 3 do
  1174.                               begin
  1175.                                  xInc := '[1, 1, 0, -1][i];
  1176.                                  yInc := '[0, 1, 1, 1][i];
  1177.                                  x2 := x + xInc; y2 := y + yInc;
  1178.                                  k := 0;
  1179.                                  while ba[x2][y2] = p do
  1180.                                     begin
  1181.                                        k := k + 10;
  1182.                                        x2 := x2 + xInc; y2 := y2 + yInc;
  1183.                                     end;
  1184.                                  if ba[x2][y2] = kEmptySquare then k := k + 1;
  1185.                                  xInc := -xInc; yInc := -yInc;
  1186.                                  x2 := x + xInc; y2 := y + yInc;
  1187.                                  while ba[x2][y2] = p do
  1188.                                     begin
  1189.                                        k := k + 10;
  1190.                                        x2 := x2 + xInc; y2 := y2 + yInc;
  1191.                                     end;
  1192.                                 if ba[x2][y2] = kEmptySquare then k := k + 1;
  1193.                                 // If this is the best move of this set, remember it
  1194.                                 if k > l then
  1195.                                     begin
  1196.                                        h1 := 0;
  1197.                                        l := k;
  1198.                                     end;
  1199.                                  // If it matches the best move so far, and it's a "good"
  1200.                                  // move, then increment it's overall value
  1201.                                  if k = l then
  1202.                                     if ((t = 1) and (l >= 40)) or
  1203.                                           (((t = 2) or (t = 3)) and (l >= 20)) then
  1204.                                        m := m + 1;
  1205.                               end;
  1206.                            // If this is the best move yet, remember it
  1207.                            if m > h1 then
  1208.                               begin
  1209.                                  h1 := m;
  1210.                                  gx := x; gy := y;
  1211.                               end;
  1212.                         end;
  1213.                // If we found a reasonable move, make it
  1214.                if h1 <> 0 then
  1215.                   begin
  1216.                      :move(kNewtonPiece, gx, gy);
  1217.                      return;
  1218.                   end;
  1219.             end;
  1220.       
  1221.          // If nothing seemed good, at least do something random
  1222.          :makeRandomMove(kNewtonPiece);
  1223.       end,
  1224.     makeRandomMove:
  1225.       func(p)
  1226.       begin
  1227.          // This is just like the default function, except it avoids
  1228.          // the edges of the board (which are really bad moves)
  1229.       
  1230.          // Try ten times to find a random move
  1231.          local i, x, y;
  1232.          for i := 1 to 10 do
  1233.             begin
  1234.                x := Random(2,squaresWide-1);
  1235.                y := Random(2,squaresHigh-1);
  1236.                if :validMove(p, x, y) then
  1237.                   begin
  1238.                      :move(p, x, y);
  1239.                      return;
  1240.                   end;
  1241.             end;
  1242.       
  1243.          // If that didn't work, just pick the first linear one
  1244.          for x := 1 to squaresWide do
  1245.             for y := 1 to squaredHigh do
  1246.                if :validMove(p, x, y) then
  1247.                   begin
  1248.                      :move(p, x, y);
  1249.                      return;
  1250.                   end;
  1251.       end,
  1252.     name: "Gomoku",
  1253.     squaresHigh: 8,
  1254.     squaresWide: 8,
  1255.     sumPieces:
  1256.       func(p, x, y)
  1257.       begin
  1258.          // Note: this function returns the number of pieces
  1259.          // in a row due to the given move, but does not include
  1260.          // the added piece -- so it returns the count minus one
  1261.       
  1262.          local ba := boardArray;
  1263.          local sum := 0;
  1264.          local i, xInc, yInc, x2, y2, s;
  1265.       
  1266.          // Try each potential direction
  1267.          for i := 0 to 3 do
  1268.             begin
  1269.                xInc := '[1, 1, 0, -1][i];
  1270.                yInc := '[0, 1, 1, 1][i];
  1271.                x2 := x + xInc; y2 := y + yInc;
  1272.                s := 0;
  1273.                while ba[x2][y2] = p do
  1274.                   begin
  1275.                      s := s + 1;
  1276.                      x2 := x2 + xInc; y2 := y2 + yInc;
  1277.                   end;
  1278.                xInc := -xInc; yInc := -yInc;
  1279.                x2 := x + xInc; y2 := y + yInc;
  1280.                while ba[x2][y2] = p do
  1281.                   begin
  1282.                      s := s + 1;
  1283.                      x2 := x2 + xInc; y2 := y2 + yInc;
  1284.                   end;
  1285.                // If this is the best sum yet, remember it
  1286.                if s > sum then sum := s;
  1287.             end;
  1288.          return sum;
  1289.       end,
  1290.     winningMove:
  1291.       func(p, x, y)
  1292.       begin
  1293.          return :sumPieces(p, x, y) >= 4;
  1294.       end,
  1295.     _proto: Board
  1296.    };
  1297. // View Gomoku is accesible from BoardGames
  1298.  
  1299.  
  1300.  
  1301.  
  1302.